/* SexpServerFormatterSink.cs -- 
   Copyright (C) 2008  Casey Marshall.

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or (at
your option) any later version.

This program is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program.  If not, see <http://www.gnu.org/licenses/>.  */


using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using System.Runtime.Remoting;
using System.Runtime.Remoting.Channels;
using System.Runtime.Remoting.Messaging;
using SexpSharp.Serialization;

namespace SexpSharp.Remoting
{
	public sealed class SexpServerFormatterSink : IServerChannelSink,
		IChannelSinkBase
	{
		private IServerChannelSink channel;
		private IChannelReceiver receiver;
        private SexpFormatter serialFormatter;
        private SexpFormatter deserialFormatter;

		public SexpServerFormatterSink(IServerChannelSink channel,
									   IChannelReceiver receiver,
                                       SexpFormatter serialFormatter,
                                       SexpFormatter deserialFormatter)
		{
			this.channel = channel;
			this.receiver = receiver;
            this.serialFormatter = serialFormatter;
            this.deserialFormatter = deserialFormatter;
		}

#region IChannelSinkBase implementation

		public IDictionary Properties {
			get {
				return null;
			}
		}

#endregion

#region IServerChannelSink

		public void AsyncProcessResponse(IServerResponseChannelSinkStack sinkStack,
										 object state, IMessage msg,
										 ITransportHeaders headers,
										 Stream stream)
		{
            IMethodReturnMessage mrm = (IMethodReturnMessage) msg;
            object callObj = null;
            if (mrm.Exception != null)
            {
                SexpFault fault = new SexpFault();
                fault.faultString = mrm.Exception.Message;
                callObj = fault;
            }
            else
            {
                SexpResponse resp = new SexpResponse();
                resp.returnValue = mrm.ReturnValue;
                resp.outArgs = mrm.OutArgs;
                callObj = resp;
            }

            ITransportHeaders rheaders = new TransportHeaders();
            if (sinkStack != null)
                stream = sinkStack.GetResponseStream(msg, rheaders);
            if (stream == null)
                stream = new MemoryStream();

            serialFormatter.Serialize(stream, callObj, null);
            if (stream is MemoryStream)
                stream.Position = 0;

            sinkStack.AsyncProcessResponse(msg, rheaders, stream);
		}

        public Stream GetResponseStream(IServerResponseChannelSinkStack sinkStack,
										object state,
										IMessage msg,
										ITransportHeaders headers)
		{
			return null;
		}

        public ServerProcessing ProcessMessage(IServerChannelSinkStack sinkStack,
											   IMessage requestMsg,
											   ITransportHeaders requestHeaders,
											   Stream requestStream,
											   out IMessage responseMsg,
											   out ITransportHeaders responseHeaders,
											   out Stream responseStream)
		{
            ServerProcessing sp;
            responseMsg = null;
            responseHeaders = null;
            responseStream = null;
            try
            {
                if (requestMsg != null)
                {
                    channel.ProcessMessage(sinkStack, requestMsg, requestHeaders,
                                           requestStream, out responseMsg,
                                           out responseHeaders, out responseStream);
                }

                object o = deserialFormatter.Deserialize(requestStream, null);
                if (!(o is SexpCall))
                    throw new RemotingException("invalid method call object: " + o);

                SexpCall call = (SexpCall) o;
                string uri = (string) requestHeaders["__RequestUri"];
                Type type = RemotingServices.GetServerTypeForUri(uri);
                if (type == null)
                    type = SexpServices.LookupType(call.typeName);
                if (type == null)
                    throw new RemotingException("target type not found: " + call.typeName);            

                List<MethodInfo> candidates = new List<MethodInfo>();

                if (call.methodName == "FieldGetter"
                    || call.methodName == "FieldSetter")
                {
                    MethodInfo methodinfo = typeof(object).GetMethod(call.methodName,
                                                                     BindingFlags.NonPublic
                                                                     | BindingFlags.Instance);
                    if (methodinfo != null)
                    {
                        System.Console.Error.WriteLine(methodinfo);
                        ParameterInfo[] param = methodinfo.GetParameters();
                        if (param.Length == call.args.Length)
                        {
                            bool paramsOk = true;
                            for (int i = 0; i < param.Length; i++)
                            {
                                if (call.args[i] == null)
                                {
                                    if (param[i].ParameterType.IsPrimitive)
                                    {
                                        paramsOk = false;
                                        break;
                                    }
                                }
                                else if (!param[i].ParameterType.IsInstanceOfType(call.args[i]))
                                {
                                    paramsOk = false;
                                    break;
                                }
                            }
                            if (paramsOk)
                                candidates.Add(methodinfo);
                        }
                    }
                    else
                        System.Console.Error.WriteLine("did not find method " + call.methodName
                                                       + " in " + type + " directly");
                }

                MethodInfo[] methods = type.GetMethods(BindingFlags.NonPublic
                                                       | BindingFlags.Public
                                                       | BindingFlags.Instance);
                foreach (MethodInfo m in methods)
                {
                    // See if the name matches.
                    if (m.Name != call.methodName)
                    {
                        bool nameFound = false;
                        object[] attrs = m.GetCustomAttributes(typeof(SexpMethodName), true);
                        if (attrs.Length > 0)
                        {
                            foreach (object attr in attrs)
                                if (((SexpMethodName) attr).MethodName == call.methodName)
                                {
                                    nameFound = true;
                                    break;
                                }
                        }

                        if (!nameFound)
                            continue;
                    }
                
                    // See if the parameters fit.
                    ParameterInfo[] param = m.GetParameters();
                    if (param.Length != call.args.Length)
                        continue;
                    bool paramsOk = true;
                    for (int i = 0; i < param.Length; i++)
                    {
                        if (call.args[i] == null)
                        {
                            if (param[i].ParameterType.IsPrimitive)
                            {
                                paramsOk = false;
                                break;
                            }
                        }
                        else if (!param[i].ParameterType.IsInstanceOfType(call.args[i]))
                        {
                            paramsOk = false;
                            break;
                        }
                    }
                    if (paramsOk)
                        candidates.Add(m);
                }

                if (candidates.Count == 0)
                    throw new RemotingException("no methods matching `" + call.methodName
                                                + "' in type `" + call.typeName + "'");

                MethodBase method = candidates[0]; // FIXME, scoring.
                requestMsg = new SexpMethodCallMessage(method, call.typeName, call.args, uri);

                sinkStack.Push(this, null);
                sp = channel.ProcessMessage(sinkStack, requestMsg, requestHeaders,
                                            null, out responseMsg, out responseHeaders,
                                            out responseStream);

                if (sp == ServerProcessing.Complete)
                {
                    if (responseMsg != null)
                    {
						if (responseHeaders == null)
							responseHeaders = new TransportHeaders();
                        if (sinkStack != null)
                            responseStream = sinkStack.GetResponseStream(responseMsg, responseHeaders);
                        if (responseStream == null)
                            responseStream = new MemoryStream();
                        WriteResponse(responseMsg, responseStream);
                        if (responseStream is MemoryStream)
                            responseStream.Position = 0;
                    }
                }
            }
            catch (Exception x)
            {
                System.Console.Error.WriteLine(x);
                if (responseHeaders == null)
                    responseHeaders = new TransportHeaders();
                if (sinkStack != null)
                    responseStream = sinkStack.GetResponseStream(responseMsg, responseHeaders);
                if (responseStream == null)
                    responseStream = new MemoryStream();
                SexpFault fault = new SexpFault();
                fault.faultString = x.Message;
                if (responseStream is MemoryStream)
                    responseStream.Position = 0;
                sp = ServerProcessing.Complete;
                //throw x;
            }

            if (sp == ServerProcessing.Complete)
                sinkStack.Pop(this);

            return sp;
		}

        public IServerChannelSink NextChannelSink
		{
			get {
				return channel;
			}
        }

#endregion

        private void WriteResponse(IMessage msg, Stream stream)
        {
            IMethodReturnMessage mrm = (IMethodReturnMessage) msg;
            object callObj = null;
            if (mrm.Exception != null)
            {
                SexpFault fault = new SexpFault();
                fault.faultString = mrm.Exception.Message;
                callObj = fault;
            }
            else
            {
                SexpResponse resp = new SexpResponse();
                resp.returnValue = mrm.ReturnValue;
                resp.outArgs = mrm.OutArgs;
                callObj = resp;
            }

            serialFormatter.Serialize(stream, callObj, null);
        }
	}
}